const std = @import("std");

pub fn decodeUInt(reader: anytype) !u64 {
    const m = try reader.readIntBig(u8);

    return switch (m) {
        0x00...0x7f => m & 0x7f,
        0xcc => try reader.readIntBig(u8),
        0xcd => try reader.readIntBig(u16),
        0xce => try reader.readIntBig(u32),
        0xcf => try reader.readIntBig(u64),
        else => error.UnexpectedMessage,
    };
}

pub fn decodeSInt(reader: anytype) !i64 {
    const m = try reader.readIntBig(u8);
    return switch (m) {
        0x00...0x7f => m & 0x7f,
        0xe0...0xff => -@bitCast(i8, (m & 0x1f)),
        0xd0 => try reader.readIntBig(i8),
        0xd1 => try reader.readIntBig(i16),
        0xd2 => try reader.readIntBig(i32),
        0xd3 => try reader.readIntBig(i64),
        else => error.UnexpectedMessage,
    };
}

pub fn decodeNil(reader: anytype) !void {
    const m = try reader.readIntBig(u8);
    return switch(m) {
        0xc0 => {},
        else => error.UnexpectedMessage,
    };
}

pub fn decodeBool(reader: anytype) !bool {
    const m = reader.readIntBig(u8);
    return switch (m) {
        0xc2 => false,
        0xc3 => true,
        else => error.UnexpectedMessage, 
    };
}

pub fn decodeFloat(reader: anytype) !f64 {
    const m = reader.readIntBig(u8);
    return switch (m) {
        0xca => blk: {
            var bytes: [4]u8 = undefined;
            try reader.read(&bytes);
            break :blk std.mem.bytesAsValue(f32, &bytes);
        },
        0xcb => blk: {
            var bytes: [8]u8 = undefined;
            try reader.read(&bytes);
            break :blk std.mem.bytesAsValue(f64, &bytes);
        },
        else => error.UnexpectedMessage, 
    };
}

pub fn decodeStringLength(reader: anytype) !usize {
    const m = try reader.readIntBig(u8);
    return switch (m) {
        0xa0...0xbf => m & 0x1f,
        0xd9 => try reader.readIntBig(u8),
        0xda => try reader.readIntBig(u16),
        0xdb => try reader.readIntBig(u32),
        else => error.UnexpectedMessage,
    };
}

pub fn decodeStringStatic(buf: []u8, reader: anytype) ![]u8 {
    const actual_len = try decodeStringLength(reader);
    if (actual_len > buf.len) {
        return error.UnexpectedMessage;
    }

    const read_length = reader.readAll(&buf[0..actual_len]);

    if (read_length != actual_len) {
        return error.UnexpectedMessage;
    }

    return buf[0..actual_len];
}

pub fn decodeStringDynamic(allocator: *std.mem.Allocator, reader: anytype) ![]u8 {
    const len = try decodeStringLength(reader);

    var result = try allocator.alloc(u8, len);
    const read_length = reader.readAll(result);

    if (read_length != len) {
        return error.UnexpectedMessage;
    }

    return result;
}

pub fn decodeBinaryLength(reader: anytype) !usize {
    const m = try reader.readIntBig(u8);
    return switch (m) {
        0xc4 => try reader.readIntBig(u8),
        0xc5 => try reader.readIntBig(u16),
        0xc6 => try reader.readIntBig(u32),
        else => error.UnexpectedMessage,
    };
}

pub fn decodeBinaryStatic(buf: []u8, reader: anytype) ![]u8 {
    const actual_len = try decodeBinaryLength(reader);
    if (actual_len != buf.len) {
        return error.UnexpectedMessage;
    }

    const read_length = reader.readAll(&buf[0..actual_len]);

    if (read_length != actual_len) {
        return error.UnexpectedMessage;
    }

    return result;
}

pub fn decodeBinaryDynamic(allocator: *std.mem.Allocator, reader: anytype) ![]u8 {
    const len = try decodeBinaryLength(reader);

    var result = try allocator.alloc(u8, len);
    const read_length = reader.readAll(result);

    if (read_length != len) {
        return error.UnexpectedMessage;
    }

    return result;
}


pub fn decodeArrayLength(reader: anytype) !usize {
    const m = try reader.readIntBig(u8);
    return switch (m) {
        0x90...0x9f => m & 0x0f,
        0xdc => try reader.readIntBig(u16),
        0xdd => try reader.readIntBig(u32),
        else => error.UnexpectedMessage,
    };
}

pub fn decodeMapLength(reader: anytype) !usize {
    const m = try reader.readIntBig(u8);
    return switch (m) {
        0x80...0x8f => m & 0x0f,
        0xde => try reader.readIntBig(u16),
        0xdf => try reader.readIntBig(u32),
        else => error.UnexpectedMessage,
    };
}

pub fn decodeExtLength(reader: anytype) !usize {
    const m = try in.readByte();
    return switch (m) {
        0xd4 => 1,
        0xd5 => 2,
        0xd6 => 4,
        0xd7 => 8,
        0xd8 => 16,
        0xc7 => try in.readIntBig(u8),
        0xc8 => try in.readIntBig(u16),
        0xc9 => try in.readIntBig(u32),
    }
}
